package com.nulldev.util.JVM.reflections.cache;

import java.lang.reflect.Field;
import java.util.Set;

public class CachedReflection {
	protected static final ClassElementFactory FACTORY = new ClassElementFactory();
	protected static final String OBJECT_NAME = Object.class.getName();

	public static final void clearCacheAll() {
		FACTORY.cacheClass.clear();
	}

	public static final void clearCache(String name) {
		FACTORY.cacheClass.remove(name);
	}

	public static final Class<?> getClass(String name) {
		return getClass(null, name);
	}

	public static final Class<?> getClass(ClassLoader loader, String name) {
		if (name == null) {
			throw new CachedReflectException("Class name not specified.");
		}
		ClassElement em = FACTORY.getClass(loader, name);
		if (em == null) {
			throw new CachedReflectException("Specified class '" + name + "' does not exist.");
		}
		return em.getClassObject();
	}

	public static final Object newInstance(String name) {
		return newInstance(null, name, (Object[]) null);
	}

	public static final Object newInstance(ClassLoader loader, String name) {
		return newInstance(loader, name, (Object[]) null);
	}

	public static final Object newInstance(String name, Object... args) {
		return newInstance(null, name, args);
	}

	public static final Object newInstance(ClassLoader loader, String clazzName, Object... args) {
		ClassElement em = FACTORY.getClass(loader, clazzName);
		if (em == null) {
			throw new CachedReflectException("Specified class '" + clazzName + "' does not exist");
		}
		if (OBJECT_NAME.equals(clazzName)) {
			return new Object();
		}
		Object ret;
		Class<?>[] types = Util.getParamsType(args);
		String superName;
		while (true) {
			if ((ret = em.newInstance(types, args, loader)) != null) {
				return ret;
			}
			superName = em.getSuperClassName();
			em = null;
			if (superName != null && !OBJECT_NAME.equals(superName)) {
				em = FACTORY.getClass(loader, superName);
			}
			if (em == null) {
				throw new CachedReflectException("Constructor of target argument does not exist for specified class '" + clazzName + "'.");
			}
		}
	}

	public static final Object newInstanceTo(ClassLoader loader, String clazzName, Object[] args, Class<?>[] types) {
		ClassElement em = FACTORY.getClass(loader, clazzName);
		if (em == null) {
			throw new CachedReflectException("Specified class '" + clazzName + "' does not exist");
		}
		if (OBJECT_NAME.equals(clazzName)) {
			return new Object();
		}
		Object ret;
		String superName;
		if (args != null && args.length > 0 && types == null) {
			types = Util.getParamsType(args);
		} else if (types != null) {
			types = Util.getParamsTypeByClass(types);
		}
		while (true) {
			if ((ret = em.newInstance(types, args, loader)) != null) {
				return ret;
			}
			superName = em.getSuperClassName();
			em = null;
			if (superName != null && !OBJECT_NAME.equals(superName)) {
				em = FACTORY.getClass(loader, superName);
			}
			if (em == null) {
				throw new CachedReflectException("Constructor of target argument does not exist for specified class '" + clazzName + "'.");
			}
		}
	}

	private static final String clazzName(final Object clazzObject, final Object target) {
		if (clazzObject == null) {
			if (target == null) {
				throw new CachedReflectException("Class object cannot be identified.");
			}
			return target.getClass().getName();
		} else if (clazzObject instanceof String) {
			if (((String) clazzObject).isEmpty()) {
				if (target == null) {
					throw new CachedReflectException("Class object cannot be identified.");
				}
				return target.getClass().getName();
			}
			return (String) clazzObject;
		} else if (clazzObject instanceof Class) {
			return ((Class<?>) clazzObject).getName();
		}
		throw new CachedReflectException("Class object cannot be identified.");
	}

	public static final boolean isField(boolean staticFlag, Object clazz, String name) {
		return isField(staticFlag, null, clazz, name);
	}

	public static final boolean isField(boolean staticFlag, ClassLoader loader, Object clazz, String name) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, null));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			if (!em.isField(staticFlag, name)) {
				superName = em.getSuperClassName();
				em = null;
				if (superName != null) {
					em = FACTORY.getClass(loader, superName);
				}
				if (em == null) {
					return false;
				}
				continue;
			}
			return true;
		}
	}

	public static final boolean isMethod(boolean staticFlag, Object clazz, String name) {
		return isMethod(staticFlag, null, clazz, name);
	}

	public static final boolean isMethod(boolean staticFlag, ClassLoader loader, Object clazz, String name) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, null));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			if (!em.isMethod(staticFlag, name)) {
				superName = em.getSuperClassName();
				em = null;
				if (superName != null) {
					em = FACTORY.getClass(loader, superName);
				}
				if (em == null) {
					return false;
				}
				continue;
			}
			return true;
		}
	}

	public static final void fieldNames(Set<String> out, boolean staticFlag, Object clazz) {
		fieldNames(out, staticFlag, null, clazz);
	}

	public static final void fieldNames(Set<String> out, boolean staticFlag, ClassLoader loader, Object clazz) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, null));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			em.fieldNames(out, staticFlag);
			superName = em.getSuperClassName();
			em = null;
			if (superName != null) {
				em = FACTORY.getClass(loader, superName);
			}
			if (em == null) {
				return;
			}
		}
	}

	public static final void methodNames(Set<String> out, boolean staticFlag, Object clazz) {
		methodNames(out, staticFlag, null, clazz);
	}

	public static final void methodNames(Set<String> out, boolean staticFlag, ClassLoader loader, Object clazz) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, null));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			em.methodNames(out, staticFlag);
			superName = em.getSuperClassName();
			em = null;
			if (superName != null) {
				em = FACTORY.getClass(loader, superName);
			}
			if (em == null) {
				return;
			}
		}
	}

	public static final void setField(String clazzName, Object target, String name, Object value) {
		setField(null, clazzName, target, name, value);
	}

	public static final void setField(ClassLoader loader, String clazzName, Object target, String name, Object value) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazzName, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			Field f = em.getField((target == null), name);
			if (f == null) {
				superName = em.getSuperClassName();
				em = null;
				if (superName != null) {
					em = FACTORY.getClass(loader, superName);
				}
				if (em == null) {
					throw new CachedReflectException("Specified field '" + name + "' does not exist in class.");
				}
				continue;
			}
			try {
				f.set(target, value);
			} catch (Exception e) {
				throw new CachedReflectException(e);
			}
			return;
		}
	}

	public static final void setField(Class<?> clazz, Object target, String name, Object value) {
		setField(null, clazz, target, name, value);
	}

	public static final void setField(ClassLoader loader, Class<?> clazz, Object target, String name, Object value) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			Field f = em.getField((target == null), name);
			if (f == null) {
				superName = em.getSuperClassName();
				em = null;
				if (superName != null) {
					em = FACTORY.getClass(loader, superName);
				}
				if (em == null) {
					throw new CachedReflectException("Specified field '" + name + "' does not exist in class.");
				}
				continue;
			}
			try {
				f.set(target, value);
			} catch (Exception e) {
				throw new CachedReflectException(e);
			}
			return;
		}
	}

	public static final Object getField(String clazzName, Object target, String name) {
		return getField(null, clazzName, target, name);
	}

	public static final Object getField(ClassLoader loader, String clazzName, Object target, String name) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazzName, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			Field f = em.getField((target == null), name);
			if (f == null) {
				superName = em.getSuperClassName();
				em = null;
				if (superName != null) {
					em = FACTORY.getClass(loader, superName);
				}
				if (em == null) {
					throw new CachedReflectException("Specified field '" + name + "' does not exist in class.");
				}
				continue;
			}
			try {
				return f.get(target);
			} catch (Exception e) {
				throw new CachedReflectException(e);
			}
		}
	}

	public static final Object getField(Class<?> clazz, Object target, String name) {
		return getField(null, clazz, target, name);
	}

	public static final Object getField(ClassLoader loader, Class<?> clazz, Object target, String name) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String superName;
		while (true) {
			Field f = em.getField((target == null), name);
			if (f == null) {
				superName = em.getSuperClassName();
				em = null;
				if (superName != null) {
					em = FACTORY.getClass(loader, superName);
				}
				if (em == null) {
					throw new CachedReflectException("Specified field '" + name + "' does not exist in class.");
				}
				continue;
			}
			try {
				return f.get(target);
			} catch (Exception e) {
				throw new CachedReflectException(e);
			}
		}
	}

	public static final Object invoke(String clazzName, Object target, String name) {
		return invoke(null, clazzName, target, name, (Object[]) null);
	}

	public static final Object invoke(ClassLoader loader, String clazzName, Object target, String name) {
		return invoke(loader, clazzName, target, name, (Object[]) null);
	}

	public static final Object invoke(Class<?> clazz, Object target, String name) {
		return invoke(null, clazz, target, name, (Object[]) null);
	}

	public static final Object invoke(ClassLoader loader, Class<?> clazz, Object target, String name) {
		return invoke(loader, clazz, target, name, (Object[]) null);
	}

	public static final Object invoke(String clazzName, Object target, String name, Object... args) {
		return invoke(null, clazzName, target, name, args);
	}

	public static final Object invoke(ClassLoader loader, String clazzName, Object target, String name, Object... args) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazzName, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		String spclazz;
		Object[] ret = new Object[1];
		Class<?>[] types = Util.getParamsType(args);
		while (true) {
			if (em.invoke(ret, target, name, loader, types, args)) {
				return ret[0];
			}
			spclazz = em.getSuperClassName();
			if (spclazz == null) {
				throw new CachedReflectException("Specified method '" + name + "' does not exist in class.");
			}
			em = FACTORY.getClass(loader, spclazz);
		}
	}

	public static final Object invoke(Class<?> clazz, Object target, String name, Object... args) {
		return invoke(null, clazz, target, name, args);
	}

	public static final Object invoke(ClassLoader loader, Class<?> clazz, Object target, String name, Object... args) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		Object[] ret = new Object[1];
		Class<?>[] types = Util.getParamsType(args);
		String spclazz;
		while (true) {
			if (em.invoke(ret, target, name, loader, types, args)) {
				return ret[0];
			}
			spclazz = em.getSuperClassName();
			if (spclazz == null) {
				throw new CachedReflectException("Specified method '" + name + "' does not exist in class.");
			}
			em = FACTORY.getClass(loader, spclazz);
		}
	}

	public static final Object invokeTo(ClassLoader loader, String clazzName, Object target, String name, Object[] args, Class<?>[] types) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazzName, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		if (args != null && args.length > 0 && types == null) {
			types = Util.getParamsType(args);
		} else if (types != null) {
			types = Util.getParamsTypeByClass(types);
		}
		Object[] ret = new Object[1];
		String spclazz;
		while (true) {
			if (em.invoke(ret, target, name, loader, types, args)) {
				return ret[0];
			}
			spclazz = em.getSuperClassName();
			if (spclazz == null) {
				throw new CachedReflectException("Specified method '" + name + "' does not exist in class.");
			}
			em = FACTORY.getClass(loader, spclazz);
		}
	}

	public static final Object invokeTo(ClassLoader loader, Class<?> clazz, Object target, String name, Object[] args, Class<?>[] types) {
		ClassElement em = FACTORY.getClass(loader, clazzName(clazz, target));
		if (em == null) {
			throw new CachedReflectException("Specified class does not exist.");
		}
		if (args != null && args.length > 0 && types == null) {
			types = Util.getParamsType(args);
		} else if (types != null) {
			types = Util.getParamsTypeByClass(types);
		}
		Object[] ret = new Object[1];
		String spclazz;
		while (true) {
			if (em.invoke(ret, target, name, loader, types, args)) {
				return ret[0];
			}
			spclazz = em.getSuperClassName();
			if (spclazz == null) {
				throw new CachedReflectException("Specified method '" + name + "' does not exist in class.");
			}
			em = FACTORY.getClass(loader, spclazz);
		}
	}
}
